package mwn.droidbus.ui;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;

import mwn.droidbus.connection.Connection;
import mwn.droidbus.connection.InformationUpdater;
import mwn.droidbus.connection.TestData;
import mwn.droidbus.elements.Bus;
import mwn.droidbus.elements.BusStop;
import mwn.droidbus.elements.Route;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.MyLocationOverlay;
import com.google.android.maps.OverlayItem;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.ToggleButton;

public class DroidBus extends MapActivity implements LocationListener {

	//user interface
	private MapView map;
	private MapController mapController;
	public static final String MENU_SHOW_ME = "Show me";
	public static final String MENU_TOGGLE_SAT = "Toggle satellite view";
	public static final String MENU_FILTER_BUSSES = "Filter busses";
	public static final String MENU_SHOWALL_BUSSES = "Show all busses";
	public static final String MENU_SHOW_STOPS = "Show/hide stops";
	private GeoPoint lastKnownLocation = new GeoPoint(-34924365,138598009);
	private HashMap<String, Integer> routesColouring;

	//components
	private LocationManager locationManager;
	private MyLocationOverlay userLocationOverlay;

	//default configuration
	private final int ZOOM_LEVEL = 15;
	private final int ZOOM_ME_LEVEL = 17;

	//Connection
	InformationUpdater iu = new Connection();
	//InformationUpdater iu = new TestData();

	//tells if the number of busses changes so we need to recolour the busses
	private int num_busses = 0;

	//Filter bus ui elements
	ListView lvRoutes;
	String[] lvArray;
	ToggleButton toggle;

	boolean drawStops = false; //User can enable drawing of stops through menu;
	boolean showMultipleRoutes = false; //Through the menu the user can compare multiple stops

	OnClickListener myCancelButtonOnClickListener = new OnClickListener() {
		public void onClick(View v) {
			setContentView(map);
		}
	};

	//Busses, Routes, BusStop
	ArrayList<Bus> busses = new ArrayList<Bus>();

	//Tapping a bus displays it's route, tap again to hide i.e. we add and remove from below arraylist
	ArrayList<Route> tappedRoutes = new ArrayList<Route>();
	CopyOnWriteArrayList<String> drawRoutes = new CopyOnWriteArrayList<String>();

	//List of all the routes - received from server on first startup of application.
	ArrayList<Route> routes = new ArrayList<Route>();
	ArrayList<BusStop> stops = new ArrayList<BusStop>();

	//Timer
	private Timer myTimer;
	private Runnable Timer_Tick = new Runnable() {
		public void run() {
			//This method runs in the same thread as the UI. Do something to the UI thread here. 
			//synchronized(iu) {
			//TODO: put this in a different thread so ui remains responsive while updating the busses
			//get the most recent list of busses
			try {
				busses = iu.getCurrentLocationAllBusses();
			} catch (Exception e) {
				Log.d("debug","Error getting location of all busses: " + e.toString());
				return;
			}
			

			Colours c = new Colours();  //***
			Colours.getColour(8,9  );					//***
			
			int new_num_busses = busses.size();
			if(new_num_busses != num_busses) {
				num_busses = new_num_busses;
				for(Bus b: busses) {
					routesColouring.put(b.getRouteID(), new Integer(  Colours.getColour(busses.indexOf(b),busses.size()   ) ) );
							
				}
			}
			//}
		}
	};

	private void TimerMethod() {
		//This method is called directly by the timer and runs in the same thread as the timer.

		//We call the method that will work with the UI through the runOnUiThread method.
		this.runOnUiThread(Timer_Tick);
	}

	public void onCreate(Bundle savedInstanceState) {

		myTimer = new Timer();
		myTimer.schedule(new TimerTask() {
			public void run() {
				TimerMethod();
			}
		},0,2000); //call every 1000milliseconds


		//TODO: not sure if want routes retreived on creation?
		try {
			routes = iu.getRoutes();
			//Log.d("debug","Size of routes array:" + routes.size());
		} catch (Exception e) {
			Log.d("debug","Error getting routes: " + e.toString());
			return;
		}

		lvArray = getListAllRoutes(); //Populate the list view with all of the routes retrieved from the information updater

		super.onCreate(savedInstanceState);

		routesColouring = new HashMap<String, Integer>(); //Instantiate the map, that maps routes to a unique colour. 

		//create a location manager, a GPS listener and bind them
		locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
		if(locationManager == null) {
			Log.d("debug", "creating location manager failed");
			return;
		}

		//create map
		map = createMap();
		if(map == null) {
			Log.d("debug","creating map failed");
			return;
		}

		/* minTime	the minimum time interval for notifications, in milliseconds. This field is only used as a hint to conserve power, and actual time between location updates may be greater or lesser than this value.
		   minDistance	the minimum distance interval for notifications, in meters
		   The frequency of notification or new locations may be controlled using the minTime and minDistance parameters. 
		   If minTime is greater than 0, the LocationManager could potentially rest for minTime milliseconds between location updates to conserve power. 
		   If minDistance is greater than 0, a location will only be broadcast if the device moves by minDistance meters. To obtain notifications as frequently as possible, set both parameters to 0.
		 */
		locationManager.requestLocationUpdates(locationManager.GPS_PROVIDER, 0, 0, this);

		Drawable defaultMarker = Markers.createDefaultMarker();


		//add an overlay showing where the user is
		userLocationOverlay = new MyLocationOverlay(this, map);
		userLocationOverlay.enableMyLocation();
		userLocationOverlay.enableCompass();
		map.getOverlays().add(userLocationOverlay);

		//add an overlay showing the routes
		RoutesOverlay routesOverlay = new RoutesOverlay(defaultMarker);
		map.getOverlays().add(routesOverlay);

		Log.d("debug","Successfully created routes overlay!");

		Log.d("debug","Successfully created bus overlay!");

		//add an overlay showing where stops are
		//TODO: stops?
		//loop over the routes and extract stops to an arraylist?
		//NOW ONLY DRAWING STOPS WHEN DRAWING THE ROUTE
		BusStopOverlay busStopOverlay = new BusStopOverlay(defaultMarker);
		map.getOverlays().add(busStopOverlay);

		//add an overlay showing where buses are
		BusOverlay busOverlay = new BusOverlay(defaultMarker);
		map.getOverlays().add(busOverlay); // place it on top of the map


		//set map as current view
		setContentView(map);

	}

	/*
	 * Creates the map, i.e. the main user interface
	 */
	private MapView createMap() {
		//TODO: generate release API key, currently using debug API key
		MapView map = new MapView(this, getString(R.string.maps_debug_api_key)); //09vEMnvT1p0yF97s5K4lM1vn7dbtajpeXX4qdzw
		mapController = map.getController();
		mapController.setZoom(ZOOM_LEVEL); // set a default zoom level
		map.setSatellite(false);
		map.setClickable(true);
		map.setBuiltInZoomControls(true); // allow user to change zoom level
		map.setTraffic(false);//disable traffic
		//Set centre of map to Adelaide
		map.getController().setCenter(degreesToGeoPoint(-34.92577,138.599732));
		return map;
	}
	private GeoPoint degreesToGeoPoint(double lat, double lon) {
		Double latitude = lat * 1000000;
		Double longitude = lon * 1000000;

		int glat = latitude.intValue();
		int glon = longitude.intValue();

		return new GeoPoint(glat,glon);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		menu.add(MENU_FILTER_BUSSES);
		menu.add(MENU_SHOWALL_BUSSES);
		menu.add(MENU_SHOW_ME);
		//menu.add(MENU_TOGGLE_SAT);
		menu.add(MENU_SHOW_STOPS);
		return true;

	}

	@Override
	public boolean onMenuItemSelected(int featureId, MenuItem item) {
		if (item.getTitle() == MENU_SHOW_ME) {
			setContentView(map);
			if (userLocationOverlay==null/*lastKnownLocation == null*/) {
				Log.d("debug", "last known location not set");
				//return false;
			} else {
				//map.getController().setCenter(userLocationOverlay.getMyLocation()/*lastKnownLocation*/);
				if(userLocationOverlay.getMyLocation()!=null) {
					map.getController().animateTo(userLocationOverlay.getMyLocation()/*lastKnownLocation*/);
					map.getController().setZoom(ZOOM_ME_LEVEL);
				} else {
					Toast.makeText(DroidBus.this, "Location unknown!", Toast.LENGTH_SHORT).show();
				}
			}

		} else if (item.getTitle() == MENU_SHOW_STOPS) {
			setContentView(map);
			if(drawStops) {
				drawStops = false;
			} else {
				drawStops = true;
			}
			//} else if (item.getTitle() == MENU_TOGGLE_SAT) {
			//	map.setSatellite(!map.isSatellite());
		} else if( item.getTitle() == MENU_FILTER_BUSSES) {
			setContentView(R.layout.alt);
			Button cancel = (Button)findViewById(R.id.Button01);

			toggle = (ToggleButton)findViewById(R.id.ToggleButton01);

			if(showMultipleRoutes) {
				toggle.setChecked(true);
			}

			toggle.setOnClickListener(new OnClickListener() {
				public void onClick(View v) {
					// Perform action on clicks
					if (toggle.isChecked()) {
						//Toast.makeText(DroidBus.this, "Yes", Toast.LENGTH_SHORT).show();
						showMultipleRoutes = true;
					} else {
						//Toast.makeText(DroidBus.this, "No", Toast.LENGTH_SHORT).show();
						showMultipleRoutes = false;
					}
				}
			});


			lvRoutes = (ListView)findViewById(R.id.ListView01);
			lvRoutes.setAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1 , lvArray));
			lvRoutes.setTextFilterEnabled(true);
			lvRoutes.setOnItemClickListener(new OnItemClickListener() {

				@Override
				public void onItemClick(AdapterView<?> arg0, View arg1,
						int position, long id) {
					/*AlertDialog.Builder adb = new AlertDialog.Builder(DroidBus.this);
					adb.setTitle("LVSelectedItemExample");
					adb.setMessage("Selected Item is = " + lvRoutes.getItemIdAtPosition(position));
					adb.setPositiveButton("Ok", null);
					adb.show();*/
					String route = lvRoutes.getItemAtPosition(position).toString();
					//lvRoutes.setItemChecked(position, true);

					addToTappedRoutes(route);
					Toast.makeText(DroidBus.this, route, Toast.LENGTH_SHORT).show();
					if(showMultipleRoutes) {
						//do nothing stay on the screen
					} else {
						setContentView(map);
					}
				}

			});
			cancel.setOnClickListener(myCancelButtonOnClickListener);

		} else if( item.getTitle()== MENU_SHOWALL_BUSSES) {
			setContentView(map);
			tappedRoutes.clear();
			drawRoutes.clear();
			stops.clear();
			map.invalidate();

		}
		return true;
	}

	private String[] getListAllRoutes() {
		String[] rts = new String[routes.size()];
		for(int i=0; i<routes.size(); i++) {
			rts[i] = routes.get(i).getRouteID();
		}
		return rts;
	}
	private void addToTappedRoutes(String route) {
		for(Route r: routes) {
			if(r.getRouteID().equals(route)) {
				//if radio button is checked we want to not clear the array beforehand
				if(!showMultipleRoutes) {
					tappedRoutes.clear();
					drawRoutes.clear();
					stops.clear();
				}
				if(!tappedRoutes.contains(r)) {
					tappedRoutes.add(r);
					drawRoutes.add(r.getRouteID());
					stops.addAll(r.getStops());
				}
				//map.invalidate();
				break;
			}
		}
	}

	@Override
	//Called whenever GPS position of user changes
	public synchronized void onLocationChanged(Location location) {

		Log.d("debug", "Location is now: " + location.getLatitude() + " " + location.getLongitude());

		int newLatitude = (int) (location.getLatitude() * 1000000);
		int newLongitude = (int) (location.getLongitude() * 1000000);
		GeoPoint newLocationPoint = new GeoPoint(newLatitude, newLongitude);

		if (newLocationPoint != lastKnownLocation) {
			lastKnownLocation = newLocationPoint;

			Log.d("debug", "Centering on: " + newLocationPoint.getLatitudeE6() + " " + newLocationPoint.getLongitudeE6());

			//mapController.animateTo(lastKnownLocation); //don't force user to scroll back to the bus that they were looking at. 
		}	
	}


	@Override
	public void onProviderDisabled(String provider) {
		// TODO Auto-generated method stub
	}

	@Override
	public void onProviderEnabled(String provider) {
		// TODO Auto-generated method stub
	}

	@Override
	public void onStatusChanged(String provider, int status, Bundle extras) {
		// TODO Auto-generated method stub
	}

	protected boolean isRouteDisplayed() {
		//Return false as we are not displaying directions on the map
		return false;
	}

	public class BusOverlay extends DroidOverlay {

		/**
		 * Instantiates a new bus overlay.
		 *
		 * @param defaultMarker the default marker
		 */
		//TODO: move bus overlay to subclass of DroidBus so that busses can be coloured the same as the routes thay are on. 
		public BusOverlay(Drawable defaultMarker) {
			super(defaultMarker);
			this.populate();
		}

		public boolean draw(Canvas canvas, MapView mapView, boolean shadow,long when) {
			super.draw(canvas, mapView, shadow);

			this.items = new ArrayList<OverlayItem>();

			ArrayList<Bus> dbusses = busses;

			for(Bus bus: dbusses) {
				OverlayItem item = new BusOverlayItem(bus,bus.getCoordinates(),bus.getID(),bus.getRouteID());

				if(drawRoutes.size()==0 || drawRoutes.contains(bus.getRouteID())) {
					Drawable marker = Markers.createBusMarker(bus.getRouteID(), Colours.getHTMLColour(routesColouring.get(bus.getRouteID())));//null);//routesColouring.get(bus.getRouteID());

					ItemizedOverlay.boundCenterBottom(marker);
					item.setMarker(marker);
					this.items.add(item);

				}
			}
			this.setLastFocusedIndex(-1);
			this.populate();
			return true;
		}

		public boolean onTap(final int x) {
			//Log.d("debug", "Bus on tap method called\n");
			BusOverlayItem tappedItem = (BusOverlayItem) this.createItem(x);
			//Log.d("debug", "after creation\n");
			if (tappedItem == null) {
				Log.d("debug", "Tapped item with index " + x + " not found");
				return false;
			}

			//Log.d("debug", "Tapped item with index " + x + ": " + tappedItem.getTitle());

			String routeID = tappedItem.getBus().getRouteID();

			//Log.d("debug","routeID found: " + routeID);

			Route r = findRoute(routeID,routes);

			if(r!=null) {
				//Log.d("debug","route not null");

				if(tappedRoutes.contains(r)) {
					//Log.d("debug","route is contained in tappedRoutes");
					tappedRoutes.remove(r);
					drawRoutes.remove(r.getRouteID());
					//Log.d("debug","removed route from tappedRoutes");
					stops.removeAll(r.getStops());//clear();
					//Log.d("debug","removed all of routes stops from stops arraylist");
				} else {
					tappedRoutes.clear();
					drawRoutes.clear();
					stops.clear();
					tappedRoutes.add(r);
					drawRoutes.add(r.getRouteID());
					stops.addAll(r.getStops());
				}
			}
			//Log.d("debug","return from onTap size of items array: " + items.size());
			return true;
		}

		private Route findRoute(String routeID, ArrayList<Route> routes) {
			Route r = null;
			for(Route x: routes) {
				if(x.getRouteID().equals(routeID)) {
					return x;
				}
			}
			return r;

		}
	}

	private class RoutesOverlay extends DroidOverlay {

		public RoutesOverlay(Drawable defaultMarker) {
			super(defaultMarker);
			this.populate();
		}

		public boolean draw(Canvas canvas, MapView mapView, boolean shadow,long when) {
			super.draw(canvas, mapView, shadow);
			ArrayList<Route> routes = tappedRoutes;

			this.items = new ArrayList<OverlayItem>();

			for (Route r : routes) {
				Paint paint = new Paint();
				paint.setColor(routesColouring.get(r.getRouteID()));
				paint.setAlpha(75); //effective transparency. 
				paint.setStrokeWidth(4f); //width of the stroke. 
				paint.setStyle(Paint.Style.STROKE);
				paint.setStrokeJoin(Paint.Join.ROUND);

				Path p = new Path();
				Point prev = null;
				//draw the route
				for (GeoPoint g : r.getPath()) {
					//draw route as lines betweeen every two points
					Point point = new Point();
					mapView.getProjection().toPixels(g, point);
					if (prev != null) {
						p.lineTo(point.x, point.y);
					} else {
						p.moveTo(point.x, point.y);
					}
					prev = point;
				}
				canvas.drawPath(p, paint);	
			}
			this.populate();
			return true;
		}
	}

	public class BusStopOverlay extends DroidOverlay {

		public BusStopOverlay(Drawable defaultMarker) {
			super(defaultMarker);
			this.populate();
		}

		public boolean draw(Canvas canvas, MapView mapView, boolean shadow,long when) {
			super.draw(canvas, mapView, shadow);

			this.items = new ArrayList<OverlayItem>();
			if(drawStops) {
				for(BusStop s: stops) {
					OverlayItem item = new StopOverlayItem(s,s.getCoordinates(),s.getStopID(),s.getName());
					Drawable marker = Markers.createStopMarker(s.getStopID());
					ItemizedOverlay.boundCenterBottom(marker);
					item.setMarker(marker);
					this.items.add(item);
				}
			}
			this.setLastFocusedIndex(-1);
			this.populate();
			return false;
		}
	}

}